#include "CGameServer.h"
#include "CNetworkManager.h"

#include <windows.h>
#include <fstream>
#include "core/loncore.h"
#include "COverlord.h"
#include "CMinion.h"

CGameServer::CGameServer()
{
  getServerConfig(); // Get server config

  cout << "Loading Minion Database..." << std::endl;
  CMinion::loadMinionBaseNew();
  minion_base = CMinion::minion_base;


  cout << "Starting Server on Port: " << server_port_ << endl;
  server = RakNetworkFactory::GetRakPeerInterface();
  server->Startup(max_connection_, 30, &SocketDescriptor(server_port_,0), 1);

  // We need to let the server accept incoming connections from the clients
  server->SetMaximumIncomingConnections(max_connection_);

  cout << "Server started." << endl;
  cout << "Welcome to Lord of Netherworld Server!" << endl;
}

CGameServer::~CGameServer()
{

}

void CGameServer::readPacket()
{
  while (server)
  {
    packet = server->Receive();

    while (packet)
    {
      BitStream bs(packet->data, packet->length, false);
      switch (packet->data[0])
      {

        case ID_LON_CHAT:
          cout << "Chat message detected." << endl;
          server->Send(&bs, HIGH_PRIORITY, RELIABLE, 0, UNASSIGNED_SYSTEM_ADDRESS, true);
          break;

        case ID_LON_LOGIN:
          cout << "Login request detected." << endl;
          loginRequest();
          break;

        case ID_LON_CREATE_ACCOUNT:
          cout << "Create account request detected." << endl;
          createAccountRequest();
          break;

        case ID_LON_LOGOUT:
          cout << "Logout request detected." << endl;
          clientDisconnect(packet->guid);
          break;

        case ID_LON_GY_ADD_MINION:
          cout << "Add minion request detected. " << endl;
          addMinionRequest();
          break;

        case ID_LON_GY_REMOVE_MINION:
          cout << "Remove minion request detected. " << endl;
          removeMinionRequest();
          break;

        case ID_LON_GAME_END:
          cout << "End of Game packet received. " << endl;
          ackGameResult();

        /// Default RakNet Messages
        case ID_REMOTE_NEW_INCOMING_CONNECTION:
          cout << "Another client has connected." << endl;
          break;

        case ID_CONNECTION_REQUEST_ACCEPTED:
          cout << "Our connection request has been accepted." << endl;
          break;

        case ID_CONNECTION_ATTEMPT_FAILED:
          cout << "Our connection request is failed." << endl;
          break;

        case ID_NEW_INCOMING_CONNECTION:
          cout << "A connection is incoming." << endl;
          break;

        case ID_NO_FREE_INCOMING_CONNECTIONS:
          cout << "The server is full." << endl;
          break;

        case ID_REMOTE_DISCONNECTION_NOTIFICATION:
          cout << "Another client has disconnected." << endl;
          break;

        case ID_REMOTE_CONNECTION_LOST:
          cout << "Another client has lost the connection." << endl;
          break;

        case ID_DISCONNECTION_NOTIFICATION:
          cout << "A client has disconnected." << endl;
          clientDisconnect(packet->guid);
          break;

        case ID_CONNECTION_LOST:
          cout << "A client lost the connection." << endl;
          clientDisconnect(packet->guid);
          break;
        default:
          cout << "Message with unknown identifier: " << int (packet->data[0]) << " has arrived." << endl;
          break;
      }
      server->DeallocatePacket(packet);

      packet = server->Receive();
    }
    //! TODO: Portability Issue
    Sleep(100);
  }
}

void CGameServer::loginRequest()
{
  COverlord *player;

  unsigned char msgId;

  // Create a bitstream to get the input data
  BitStream bs(packet->data, packet->length, false);
  BitStream reply;
  // Buffer out the message ID
  bs.Read(msgId);

  stringw username(CNetworkIO::readWcs(bs));
  stringw password(CNetworkIO::readWcs(bs));

  stringc cStrUser = username;
  cout << "Username: " << cStrUser.c_str() << endl;

  stringc cStrPass = password;
  cout << "Password: " << cStrPass.c_str() << endl;

  // Find the Player
  player = COverlord::playerLogin(cStrUser.c_str(), cStrPass.c_str());
  //if ( player && playerLocks.find(username) == 0)
  if ( player )
  {
    std::cout << "Login sucess" << endl;

    //playerLocks.set(username, 1);
    player_list[packet->guid] = player->player_id();

    // Write the message ID
    msgId = ID_LON_LOGIN_SUCESS;
    reply.Write(msgId);

    // Convert the text into binary data for bitstream transfer
    player->netWrite(reply);

    //! Minions details
    reply.Write(player->getMinionCount());
    for ( u32 i=0; i<player->getMinionCount(); i++ )
    {
      reply.Write(player->getMinion(i)->minion_id());
      player->getMinion(i)->getStats().netWrite(reply);
    }
    server->Send(&reply, HIGH_PRIORITY, RELIABLE, 0, packet->systemAddress, false);
  }
  else
  {
    // Write the message ID
    msgId = ID_LON_LOGIN_FAIL;
    reply.Write(msgId);

    server->Send(&reply, HIGH_PRIORITY, RELIABLE, 0, packet->systemAddress, false);

    std::cout << "Incorrect username or password." << std::endl;
  }
}

void CGameServer::createAccountRequest()
{
  u8 msgId;
  u8 type;

  BitStream bs(packet->data, packet->length, false);

  // Write the message ID
  bs.Read(msgId);

  stringw login = CNetworkIO::readWcs(bs);
  stringw password = CNetworkIO::readWcs(bs);
  stringw name = CNetworkIO::readWcs(bs);

  bs.Read(type);

  msgId = ID_LON_CREATE_ACCOUNT;

  BitStream reply;
  reply.Write(msgId);

  // New insert
  CDalOverlord dal;
  stringc cStrUser = login;
  stringc cStrPass = password;
  stringc cStrName = name;

  bool result = dal.insertPlayerData( cStrUser.c_str(), // username / login
                                      cStrPass.c_str(), // password
                                      cStrName.c_str(), // In Game Name
                                      type,             // Overlord type code
                                      0,                // Overlord title code
                                      0,                // Fame
                                      0,                // Win Count
                                      0);               // Lose Count

  reply.Write(result);

  server->Send(&reply, HIGH_PRIORITY, RELIABLE, 0, packet->systemAddress, false);
}

void CGameServer::addMinionRequest()
{
  u8 msgId;
  u8 type;

  // Create a bitstream to get the input data
  BitStream bs(packet->data, packet->length, false);
  bs.Read(msgId); // Buffer out the message ID
  bs.Read(type);  // Read the type of minion to be added

  stringw name = CNetworkIO::readWcs(bs);

  u32 player_id = player_list[packet->guid];
  CMinion *pMinion = new CMinion(minion_base[type]);
  pMinion->getStats().name = name;

  // Save to database
  CDalMinion dal;
  stringc csName = name;
  minionStats stat = pMinion->getStats();
  dal.insertMinion( csName.c_str(),
                    stat.hp,
                    stat.mp,
                    stat.atk,
                    stat.def,
                    stat.mAtk,
                    stat.mDef,
                    stat.speed,
                    (int) floor(stat.range),
                    (int) floor(stat.movement),
                    stat.level,
                    type,
                    player_id);

  u32 minion_id = (u32) dal.getLastInsertId("lonserverdb");

  // Now we send the reply to the clients about the newly created minion
  BitStream reply;
  reply.Write(msgId);
  reply.Write(minion_id);
  pMinion->getStats().netWrite(reply);
  delete pMinion;

  server->Send(&reply, HIGH_PRIORITY, RELIABLE, 0, packet->systemAddress, false);
}

void CGameServer::removeMinionRequest()
{
  u8 msgId;
  u32 minion_id;
  // Create a bitstream to get the input data
  BitStream bs(packet->data, packet->length, false);
  bs.Read(msgId); // Buffer out the message ID
  bs.Read(minion_id);    // Read the minion id to be removed

  CDalMinion dal;
  dal.removeMinion(minion_id);

  server->Send(&bs, HIGH_PRIORITY, RELIABLE, 0, packet->systemAddress, false);
}

void CGameServer::clientDisconnect(RakNetGUID &guid)
{
  std::map<RakNetGUID, int>::iterator it = player_list.find(guid);
  if (it != player_list.end())
  {
    player_list.erase(it);
  }
  else
    cout << "Disconnected client is not logged in. No data saved." << endl;
}

/** @brief ackGameResult
  *
  * @todo: document this function
  */
void CGameServer::ackGameResult()
{
  u8 msgId;
  u32 player_id;
  bool result;

  BitStream bs(packet->data, packet->length, false);
  bs.Read(msgId);
  bs.Read(player_id);
  bs.Read(result);

  CDalOverlord dal;
  dal.updateGameResult(player_id, result);
}



void CGameServer::getServerConfig()
{
  INIReader reader("server.ini");

  if (reader.ParseError() < 0) {
      std::cout << "Can't load 'server.ini'\n";

      // Default values
      server_port_ = 15337;
      max_connection_ = 10;
  }

  server_port_ = reader.GetInteger("server", "port", 15337);
  max_connection_ = reader.GetInteger("server", "max-connection", 10);
  std::cout << "Config loaded from 'server.ini': "
            << "\nport = " << server_port_
            << "\nmax-connection = " << max_connection_ << std::endl;
}
